package com.liang.lingdong.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.ShearCaptcha;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.extra.mail.MailUtil;
import com.liang.lingdong.common.ErrorCode;
import com.liang.lingdong.constant.CommonConstant;
import com.liang.lingdong.constant.UserConstant;
import com.liang.lingdong.enums.UserStatusEnum;
import com.liang.lingdong.exception.BusinessException;
import com.liang.lingdong.model.auth.dto.UserJoinDto;
import com.liang.lingdong.model.auth.dto.UserLoginByEmailDto;
import com.liang.lingdong.model.auth.dto.UserLoginDto;
import com.liang.lingdong.model.auth.dto.UserSendEmailDto;
import com.liang.lingdong.model.auth.vo.AuthCodeVo;
import com.liang.lingdong.model.auth.vo.UserLoginVo;
import com.liang.lingdong.model.user.User;
import com.liang.lingdong.model.user.vo.UserVo;
import com.liang.lingdong.service.AuthService;
import com.liang.lingdong.service.UserService;
import com.liang.lingdong.utils.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * 鉴权服务
 */
@Slf4j
@Service
public class AuthServiceImpl implements AuthService {


    @Resource
    private RedisTemplate<String, String> redisTemplate;

    @Resource
    private UserService userService;

    /**
     * 获取验证码
     * @return 结果
     */
    @Override
    public AuthCodeVo getAuthCode() {
        //定义图形验证码的长、宽、验证码字符数、干扰线宽度
        ShearCaptcha captcha = CaptchaUtil.createShearCaptcha(110, 40, 6, 4);
        // 获取答案
        String answerCode = captcha.getCode();
        log.info("验证码答案为：{}", answerCode);
        // 生成UUID
        String codeUid = UUID.randomUUID().toString();
        // 获取base64验证码
        String imageBase64Data = captcha.getImageBase64Data();
        // 构建redis key
        String redisKey = RedisUtils.buildKey(CommonConstant.AUTH_CODE_REDIS_PREFIX, codeUid);
        // 存储答案内容
        redisTemplate.opsForValue().set(redisKey, answerCode, 5, TimeUnit.MINUTES);
        return AuthCodeVo.builder()
                .codeId(codeUid)
                .codeImage(imageBase64Data)
                .build();
    }

    /**
     * 校验验证码
     * @param uid 验证码id
     * @param answer 答案
     * @return 结果
     */
    @Override
    public boolean checkAuthCode(String uid, String answer, String key) {
        if (StringUtils.isAnyBlank(uid, answer)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"请输入验证码");
        }
        // 构建redis key
        String redisKey = RedisUtils.buildKey(key, uid);
        if (Boolean.FALSE.equals(redisTemplate.hasKey(redisKey))) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"验证码已过期");
        }
        // 判断答案是否正确
        String redisAnswerCode = redisTemplate.opsForValue().get(redisKey);
        if (StringUtils.isAnyBlank(redisAnswerCode)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"验证码已失效");
        }
        // 统一转换大小写
        answer = answer.toLowerCase();
        redisAnswerCode = redisAnswerCode.toLowerCase();
        if (!answer.equals(redisAnswerCode)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"验证码不正确");
        }
        return true;
    }

    /**
     * 销毁验证码
     * @param uid 验证码id
     * @return 结果
     */
    @Override
    public boolean destroyAuthCode(String uid, String key) {
        String redisKey = RedisUtils.buildKey(CommonConstant.AUTH_CODE_REDIS_PREFIX, uid);
        return Boolean.TRUE.equals(redisTemplate.delete(redisKey));
    }


    /**
     * 用户加入我们
     * @param userJoinDto 用户参数
     * @return 结果
     */
    @Override
    public boolean userJoin(UserJoinDto userJoinDto) {
        String username = userJoinDto.getUsername();
        String userAccount = userJoinDto.getUserAccount();
        String email = userJoinDto.getEmail();
        String uid = userJoinDto.getUid();
        String authCode = userJoinDto.getAuthCode();
        String userDesc = userJoinDto.getUserDesc();
        String className = userJoinDto.getClassName();
        Integer sex = userJoinDto.getSex();

        // 校验邮箱格式
        boolean isEmail = Validator.isEmail(email);
        if (!isEmail) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"邮箱格式不正确");
        }
        // 校验验证码
        checkAuthCode(uid, authCode, CommonConstant.AUTH_CODE_REDIS_PREFIX);
        // 校验账户是否存在
        List<User> userList = userService.lambdaQuery()
                .eq(User::getUserAccount, userAccount)
                .list();
        if (!userList.isEmpty()) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"账户已存在无法重复注册");
        }
        // 构建插入对象
        User user = User.builder()
                .userAccount(userAccount)
                .username(username)
                .email(email)
                .userDesc(userDesc)
                .className(className)
                .sex(sex)
                .status(UserStatusEnum.REVIEWING.getCode())
                .build();
        // 保存
        boolean save = userService.save(user);
        if (!save) {
            throw new BusinessException(ErrorCode.SYSTEM_ERROR);
        }
        destroyAuthCode(uid, CommonConstant.AUTH_CODE_REDIS_PREFIX);
        return true;
    }

    /**
     * 用户登录
     * @param userLoginDto 用户登录参数
     * @return 结果
     */
    @Override
    public UserLoginVo userLogin(UserLoginDto userLoginDto) {
        String userAccount = userLoginDto.getUserAccount();
        String userPassword = userLoginDto.getUserPassword();
        String uId = userLoginDto.getUId();
        String authCode = userLoginDto.getAuthCode();

        // 校验验证码
        checkAuthCode(uId, authCode, CommonConstant.AUTH_CODE_REDIS_PREFIX);
        // 查询数据库
        User user= userService.lambdaQuery()
                .eq(User::getUserAccount, userAccount)
                .one();
        if (user == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"用户不存在");
        }
        // 校验密码
        String encryptPassword = DigestUtil.md5Hex(UserConstant.SALT + userPassword);
        // 判断密码是否正确
        if (!encryptPassword.equals(user.getUserPassword())) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"密码不正确");
        }
        // 判断用户状态
        if (!UserStatusEnum.NORMAL.getCode().equals(user.getStatus())) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"账户已失效或被封禁");
        }
        // 登录
        StpUtil.login(user.getBusinessId());
        // 获取token
        String token = StpUtil.getTokenValue();
        destroyAuthCode(uId, CommonConstant.AUTH_CODE_REDIS_PREFIX);
        return UserLoginVo.builder()
                .token(token)
                .username(user.getUsername())
                .build();
    }

    /**
     * 发送邮件
     * @param userSendEmailDto 邮件参数
     * @return 结果
     */
    @Override
    public String sendEmail(UserSendEmailDto userSendEmailDto) {
        String email = userSendEmailDto.getEmail();
        String authCode = userSendEmailDto.getAuthCode();
        String uid = userSendEmailDto.getUid();
        // 校验邮箱格式
        boolean isEmail = Validator.isEmail(email);
        if (!isEmail) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"邮箱格式不正确");
        }
        // 校验验证码
        checkAuthCode(uid, authCode, CommonConstant.AUTH_CODE_REDIS_PREFIX);
        // 校验邮箱是否存在
        List<User> userList = userService.lambdaQuery()
                .eq(User::getEmail, email)
                .ne(User::getStatus, UserStatusEnum.BAN.getCode())
                .list();
        if (userList.isEmpty()) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"邮箱未注册,无法发送验证码");
        }
        // 获取用户
        User user = userList.get(0);
        // 生成验证码生成频率key
        String redisCodeFrequencyKey = RedisUtils.buildKey(CommonConstant.EMAIL_CODE_FREQUENCY_REDIS_PREFIX, email);
        if (Boolean.TRUE.equals(redisTemplate.hasKey(redisCodeFrequencyKey))) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"验证码发送太频繁,请稍后再试");
        }
        redisTemplate.opsForValue().set(redisCodeFrequencyKey,
                Optional.ofNullable(user)
                        .map(User::getBusinessId)
                        .map(String::valueOf)
                        .orElse("code")
                , 1, TimeUnit.MINUTES);
        // 发送验证码
        String emailAuthCode = String.valueOf(RandomUtil.randomInt(100000, 999999));;
        // 拼接邮件内容
        String emailContent = "欢迎使用灵动创意工坊综合管理平台，您的验证码为：" + emailAuthCode + "，请在5分钟内完成验证。";
        // 发送邮件
        try {
            MailUtil.send(email, "灵动创意工坊", emailContent, false);
        } catch (Exception e) {
            log.error("邮件发送失败, 邮箱: {}, 错误: {}",email, e.getMessage());
            throw new BusinessException(ErrorCode.SYSTEM_ERROR,"邮件发送失败，请更换其他验证方式");
        }
        // 生成验证码id
        String codeId = UUID.randomUUID().toString();
        // 向redis中存储验证码
        String redisCodeKey = RedisUtils.buildKey(CommonConstant.EMAIL_CODE_REDIS_PREFIX, codeId);
        // 存储验证码
        redisTemplate.opsForValue().set(redisCodeKey, emailAuthCode, 5, TimeUnit.MINUTES);
        destroyAuthCode(uid, CommonConstant.AUTH_CODE_REDIS_PREFIX);
        return codeId;
    }

    /**
     * 用户登录
     * @param userLoginByEmailDto 用户登录参数
     * @return 结果
     */
    @Override
    public UserLoginVo userLoginByEmail(UserLoginByEmailDto userLoginByEmailDto) {
        String email = userLoginByEmailDto.getEmail();
        String authCode = userLoginByEmailDto.getAuthCode();
        String uid = userLoginByEmailDto.getUid();
        // 校验验证码
        checkAuthCode(uid, authCode, CommonConstant.EMAIL_CODE_REDIS_PREFIX);
        // 校验邮箱格式
        boolean isEmail = Validator.isEmail(email);
        if (!isEmail) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"邮箱格式不正确");
        }
        // 校验邮箱是否存在
        List<User> userList = userService.lambdaQuery()
                .eq(User::getEmail, email)
                .ne(User::getStatus, UserStatusEnum.BAN.getCode())
                .list();
        if (userList.isEmpty()) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"邮箱未注册,无法登录");
        }
        User user = userList.get(0);
        StpUtil.login(user.getBusinessId());
        String token = StpUtil.getTokenValue();
        destroyAuthCode(uid, CommonConstant.EMAIL_CODE_REDIS_PREFIX);
        return UserLoginVo.builder()
                .token(token)
                .username(user.getUsername())
                .build();
    }

    /**
     * 获取登录用户信息
     * @return 结果
     */
    @Override
    public UserVo loginUserInfo() {
        long loginUserId = StpUtil.getLoginIdAsLong();
        return userService.getUserInfo(loginUserId);
    }
}
